﻿// #define USE_DUNGEONBUDDY_DLL

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
using System.Windows.Media;
using Bots.DungeonBuddy.Avoidance;
using Bots.DungeonBuddy.Behaviors;
using Bots.DungeonBuddy.Profiles.Handlers;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Styx.WoWInternals.World;
using Action = Styx.TreeSharp.Action;
#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;
using AvoidanceManager = Bots.DungeonBuddyDll.Avoidance.AvoidanceManager;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
    using AvoidanceManager = Bots.DungeonBuddy.Avoidance.AvoidanceManager;

namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{
    public class ValeOfEternalSorrows : Dungeon
    {
        #region Overrides of Dungeon

        private readonly WoWPoint _entrance = new WoWPoint(1242.479, 600.5036, 318.0787);

        public override uint DungeonId
        {
            get { return 716; }
        }

        public override WoWPoint Entrance
        {
            get
            {
                if (_runningToBoss)
                {
                    _runningToBoss = false;
                    _diedOnTrashCount++;
                    Logger.Write(
                        "You have died {0} times on trash while trying to run to group. {1} more attempts before leaving group.",
                        _diedOnTrashCount,
                        3 - _diedOnTrashCount);
                }
                return _entrance;
            }
        }

        #region Movement

        private static WoWPoint _scarredValeCorner1 = new WoWPoint(1215.411, 979.4381, 417.3395);
        private static WoWPoint _scarredValeCorner2 = new WoWPoint(970.0, 809.8154, 505.797);

        private static readonly WoWPoint ScarredValeEndPoint = new WoWPoint(953.0652, 916.4423, 380.1422);

        private static readonly WoWPoint _scaredValeRightPackLoc = new WoWPoint(1183.543, 956.5555, 416.1801);
        private static readonly WoWPoint _scaredValeRightPackLoc2 = new WoWPoint(1109.037, 910.0849, 404.3054);
        private static readonly WoWPoint _scaredValeleftPackLoc = new WoWPoint(1226.699, 923.8916, 401.4344);
        private static readonly WoWPoint _scaredValeleftPackLoc2 = new WoWPoint(1244.36, 875.6937, 388.3029);
        private static readonly WoWPoint _scaredValeMiddlePackLoc = new WoWPoint(1097.468, 875.1085, 399.4137);

        private readonly CircularQueue<WoWPoint> _scarredValeLeftPath = new CircularQueue<WoWPoint>()
                                                                        {
                                                                            new WoWPoint(1244.36, 875.6937, 388.3029),
                                                                            new WoWPoint(1009.988, 818.8098, 381.5305),
                                                                            ScarredValeEndPoint
                                                                        };

        private readonly CircularQueue<WoWPoint> _scarredValeMiddlePath = new CircularQueue<WoWPoint>()
                                                                          {
                                                                              new WoWPoint(1191.8, 934.6475, 410.969),
                                                                              new WoWPoint(1125.582, 858.9927, 402.1305),
                                                                              ScarredValeEndPoint
                                                                          };

        private readonly CircularQueue<WoWPoint> _scarredValeRightPath = new CircularQueue<WoWPoint>()
                                                                         {
                                                                             new WoWPoint(1153.891, 942.6133, 417.034),
                                                                             new WoWPoint(1079.709, 923.767, 398.7296),
                                                                             ScarredValeEndPoint
                                                                         };

        private CircularQueue<WoWPoint> _scarredValePath;

        private bool TakeScarredValeRightPath
        {
            get
            {
                return !ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _scaredValeRightPackLoc, 15).Any() &&
                       !ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _scaredValeRightPackLoc2, 15).Any();
            }
        }

        private bool TakeScarredValeMiddlePath
        {
            get { return !ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _scaredValeMiddlePackLoc, 15).Any(); }
        }


        private bool TakeScarredValeLeftPath
        {
            get
            {
                return !ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _scaredValeleftPackLoc, 15).Any() &&
                       !ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _scaredValeleftPackLoc2, 15).Any();
            }
        }

        public override MoveResult MoveTo(WoWPoint location)
        {
            var myLoc = Me.Location;
            // if player is in the Scarred Vale and moveto location is outside the scardVale and further than 100 units away then follow a defined path through the area.
            if (PointIsInScarredVale(myLoc) && (!PointIsInScarredVale(location) || myLoc.DistanceSqr(location) > 100*100))
            {
                if (_scarredValePath == null)
                {
                    if (TakeScarredValeLeftPath)
                    {
                        _scarredValePath = _scarredValeLeftPath;
                    }
                    else if (TakeScarredValeRightPath)
                    {
                        _scarredValePath = _scarredValeRightPath;
                    }
                    else if (TakeScarredValeMiddlePath)
                    {
                        _scarredValePath = _scarredValeMiddlePath;
                    }
                    else
                    {
                        Alert.Show(
                            "Unable to find path through Scarred Vale  to group",
                            "Automatically leaving group because bot is unable to find path through Scarred Vale to group. Press 'Continue' to stay and manually path to group",
                            30,
                            true,
                            true,
                            () => _diedOnTrashCount = 0,
                            () => Lua.DoString("LeaveParty()"),
                            "Continue",
                            "Leave");
                    }
                }
                if (_scarredValePath != null)
                {
                    var moveTo = _scarredValePath.Peek();
                    if (moveTo.Distance(myLoc) < 3)
                    {
                        if (moveTo == ScarredValeEndPoint)
                            return MoveResult.Failed;
                        _scarredValePath.Dequeue();
                        moveTo = _scarredValePath.Peek();
                    }
                    return Navigator.MoveTo(moveTo);
                }
            }
            if (_scarredValePath != null && _scarredValePath.Peek() != _scarredValePath.First)
            {
                _scarredValePath.CycleTo(_scarredValePath.First);
            }

            _runningToBoss = myLoc.DistanceSqr(location) > 100*100 && !Me.IsGhost;
            return MoveResult.Failed;
        }

        private bool PointIsInScarredVale(WoWPoint point)
        {
            return point.X <= _scarredValeCorner1.X && point.Y <= _scarredValeCorner1.Y && point.X >= _scarredValeCorner2.X && point.Y >= _scarredValeCorner2.Y;
        }

        #endregion

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                {
                    var unit = ret.ToUnit();
                    if (unit != null)
                    {
                        if ((unit.Entry == HeSoftfootId || unit.Entry == RookStonetoeId || unit.Entry == SunTenderheartId) && unit.CurrentHealth <= 1)
                            return true;
                    }
                    return false;
                });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == ShaPuddleId)
                        outgoingunits.Add(unit);
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    switch (unit.Entry)
                    {
                        case EmbodiedMiseryId: // Fallen Protectors encounter add
                            priority.Score += 2000;
                            continue;
                        case EmbodiedGloomId: // Fallen Protectors encounter add
                        case ReflectionId: // Sha of Pride encounter add
                        case EmbodiedSorrowId: // Fallen Protectors encounter add
                            priority.Score += 3000;
                            continue;
                        case EmbodiedAnguishId: // Fallen Protectors encounter add
                            priority.Score += 5000;
                            continue;
                        case EssenceofCorruptionId: // Norushen encounter add
                        case EssenceofCorruptionPhasedId: // Norushen Look Within dps only encounter add
                            priority.Score = 3000 - unit.Distance; //  remove all previous weights and only factor distance in the final weight.
                            continue;
                        case ManifestationofCorruptionId: // Norushen encounter add
                        case ManifestationofCorruptionPhasedId: // Norushen Look Within dps only encounter add
                            priority.Score = 4000 - unit.Distance; //  remove all previous weights and only factor distance in the final weight.
                            continue;
                        case ShaPuddleId: // Immerseus encounter. 
                        case DesperationSpawnId: //  Fallen Protectors encounter. 
                            priority.Score = 2000 - unit.Distance; // remove all previous weights and only factor distance in the final weight.
                            continue;
                        case ManifestationOfPrideId: // Sha of Pride encounter add
                            if (Me.IsRange())
                                priority.Score += 3000;
                            continue;
                    }
                }
            }
        }

        public override void OnEnter()
        {
            _entranceTrash = new List<DynamicBlackspot>
                             {
                                 new DynamicBlackspot(
                                     ShouldAvoidLeftSide,
                                     () => _leftEntranceTrashPackLoc,
                                     LfgDungeon.MapId,
                                     15,
                                     10,
                                     "Left Entrance Steps"),
                                 new DynamicBlackspot(
                                     ShouldAvoidRightSide,
                                     () => _rightEntranceTrashPackLoc,
                                     LfgDungeon.MapId,
                                     15,
                                     10,
                                     "Right Entrance Steps"),
                             };

            DynamicBlackspotManager.AddBlackspots(_entranceTrash);

            if (Me.IsTank())
            {
                Alert.Show(
                    "Tanking Not Supported",
                    string.Format(
                        "Tanking is not supported in the {0} script. If you wish to stay in raid and play manually then press 'Continue'. Otherwise you will automatically leave raid.",
                        Name),
                    30,
                    true,
                    true,
                    null,
                    () => Lua.DoString("LeaveParty()"),
                    "Continue",
                    "Leave");
            }
            else
            {
                Alert.Show(
                    "Do Not AFK",
                    "It is highly recommended you do not afk while in a raid and be prepared to intervene if needed in the event something goes wrong or you're asked to perform a certain task.",
                    20,
                    true,
                    false,
                    null,
                    null,
                    "Ok");
            }

            if (HealTargeting.Instance != null)
            {
                HealTargeting.Instance.IncludeTargetsFilter += HealTargeting_IncludeTargetsFilter;
                HealTargeting.Instance.RemoveTargetsFilter += HealTargeting_RemoveTargetsFilter;
            }

            _scarredValePath = null;
        }

        public override void OnExit()
        {
            if (HealTargeting.Instance != null)
            {
                HealTargeting.Instance.IncludeTargetsFilter -= HealTargeting_IncludeTargetsFilter;
                HealTargeting.Instance.RemoveTargetsFilter -= HealTargeting_RemoveTargetsFilter;
            }
            DynamicBlackspotManager.RemoveBlackspots(_entranceTrash);
            _entranceTrash = null;
        }

        private void HealTargeting_IncludeTargetsFilter(List<WoWObject> incomingUnits, HashSet<WoWObject> outgoingUnits)
        {
            if (!Me.IsHealer())
                return;
            // because some combat routine's HealTargeting class will only include WoWPlayer types we will need to pull any other type from ObjectManager directly
            foreach (var healTarget in ObjectManager.ObjectList.Where(o => _healTargetIds.Contains(o.Entry)))
            {
                outgoingUnits.Add(healTarget);
            }
        }

        private void HealTargeting_RemoveTargetsFilter(List<WoWObject> units)
        {
            bool immerseusEncounterInProcess = _immerseus != null && _immerseus.IsValid && _immerseus.Combat;
            units.RemoveAll(
                o =>
                {
                    var unit = o as WoWUnit;
                    if (unit == null) return false;
                    // remove all heal targets that are not in range. Everyone is spread out so just heal stuff that is in range.
                    if (immerseusEncounterInProcess && unit.Distance > 40)
                        return true;
                    return false;
                    ;
                });
        }

        #endregion

        #region Root Behavior

        private const uint ShaVortexMissileSpellVisualId = 33181;
        private const uint VortexMissileSpellId = 147308;

        private const int SwirlZoneVisualId = 33691;


        private readonly uint[] _healTargetIds =
        {
            RookStonetoeLookWithinHealerSpawnId, SunTenderheartLookWithinHealerSpawnId, LevenDawnbladeLookWithinHealerSpawnId,
            ContaminatedPuddleId
        };

        private readonly WoWPoint _leftEntranceTrashPackLoc = new WoWPoint(1365.71, 419.9323, 263.1555);
        private readonly WoWPoint _rightEntranceTrashPackLoc = new WoWPoint(1527.006, 428.5891, 261.0348);
        private WaitTimer LeftSideTimer = new WaitTimer(TimeSpan.FromSeconds(5));
        private WaitTimer RightSideTimer = new WaitTimer(TimeSpan.FromSeconds(5));

        // count the number of times bot dies to trash while running back to group after a wipe.  Bot will automatically leave party if this happens too often.
        private int _diedOnTrashCount;
        private List<DynamicBlackspot> _entranceTrash;
        private bool _runningToBoss;
        // TODO: Add logic that can successfuly navigate Scarred Vale because each group clears a different path through it.  
        [EncounterHandler(0)]
        public Composite RootBehavior()
        {
            // Spirling missiles used by trash on the way to Immereus 
            AddAvoidLocation(ctx => true, 2, o => ((WoWMissile) o).Position, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == VortexMissileSpellId));
            // Rushing Waters abilty used by trash on the way to Immereus 
            AddAvoidObject(ctx => true, 2, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellVisualId == SwirlZoneVisualId && o.Distance < 20);
            // Avoid these missles while clearing Sha of Pride trash 
            AddAvoidLocation(
                ctx => true,
                4,
                o => ((WoWMissile) o).ImpactPosition,
                () => WoWMissile.InFlightMissiles.Where(m => m.SpellVisualId == ShaVortexMissileSpellVisualId));

            return
                new PrioritySelector(
                    new Decorator(
                        ctx => _diedOnTrashCount >= 3,
                        new Action(
                            ctx =>
                                Alert.Show(
                                    "Unable to travel to group",
                                    "Automatically leaving group because bot is unable to travel to group because of getting aggro and dieing on the way. Press 'Continue' to stay",
                                    30,
                                    true,
                                    true,
                                    () => _diedOnTrashCount = 0,
                                    () => Lua.DoString("LeaveParty()"),
                                    "Continue",
                                    "Leave"))));
        }


        private bool ShouldAvoidLeftSide()
        {
            if (!LeftSideTimer.IsFinished) return true;
            var aliveTrash = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _leftEntranceTrashPackLoc, 20, unit => unit.IsHostile).Any();
            if (aliveTrash)
                LeftSideTimer.Reset();
            var aliveTrashOnRight = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _rightEntranceTrashPackLoc, 20, unit => unit.IsHostile).Any();
            return aliveTrash && !aliveTrashOnRight;
        }

        private bool ShouldAvoidRightSide()
        {
            if (!RightSideTimer.IsFinished) return true;
            var aliveTrash = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _rightEntranceTrashPackLoc, 20, unit => unit.IsHostile).Any();
            if (aliveTrash)
                RightSideTimer.Reset();
            var aliveTrashOnLeft = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => _leftEntranceTrashPackLoc, 20, unit => unit.IsHostile).Any();
            return aliveTrash && !aliveTrashOnLeft;
        }

        #endregion

        #region Immerseus

        private const uint ImmerseusId = 71543;
        private const uint ShaPuddleId = 71603;
        private const uint ContaminatedPuddleId = 71604;
        private const uint LesserShaPuddleId = 73197;
        private const uint SwirlId = 71548;
        private const int SwirlTargetId = 71550;
        private const uint ShaSplashSpellId = 143298;
        private const uint SwirlSpellId = 143410;
        private const uint ShaBoltId = 71544;
        private const uint SeepingShaSpellId = 143281;
        private WoWUnit _immerseus = null;

        [EncounterHandler(71543, "Immerseus", Mode = CallBehaviorMode.Proximity, BossRange = 90)]
        public Composite ImmerseusBehavior()
        {
            WoWPoint outOfCombatWaitPoint = WoWPoint.Zero;

            // Avoid moving on top of the boss because he does an aoe centered at his location that does a lot of damage and a knockback
            AddAvoidObject(ctx => true, 31, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == SeepingShaSpellId);
            // avoid the sha splashes caused by Sha Bolt.
            AddAvoidObject(ctx => true, 3, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == ShaSplashSpellId && o.Distance < 20);
            // Swirl
            AddAvoidObject(ctx => true, 3, o => o.Entry == SwirlId && o.ToUnit().HasAura("Swirl") && o.Distance < 20);
            return new PrioritySelector(
                ctx => _immerseus = ctx as WoWUnit,
                // out of combat behavior
                new Decorator(
                    ctx => !_immerseus.Combat && !_immerseus.IsFriendly,
                    new PrioritySelector(
                        // initialize the 'outOfCombatWaitPoint' location
                        new Decorator(
                            ctx => outOfCombatWaitPoint == WoWPoint.Zero,
                            new Action(
                                ctx =>
                                    outOfCombatWaitPoint =
                                        GetRandomPointAroundLocation(
                                            _immerseus.Location,
                                            (int) _immerseus.RotationDegrees + (Me.IsMelee() ? 120 : 60),
                                            (int) _immerseus.RotationDegrees + (Me.IsMelee() ? 240 : 300),
                                            31,
                                            Me.IsMelee() ? _immerseus.MeleeRange() : _immerseus.CombatReach + 35))),
                        // wait at the 'outOfCombatWaitPoint' location unti encounter starts.
                        new Decorator(ctx => outOfCombatWaitPoint.Distance(Me.Location) > 10, new Action(ctx => Navigator.MoveTo(outOfCombatWaitPoint))),
                        new Decorator(ctx => outOfCombatWaitPoint.Distance(Me.Location) <= 10, new ActionAlwaysSucceed()))),
                // in combat behavior
                new Decorator(
                    ctx => _immerseus.Combat,
                    new PrioritySelector(
                        // reset the 'outOfCombatWaitPoint' value when entering combat.
                        new Decorator(ctx => outOfCombatWaitPoint != WoWPoint.Zero, new Action(ctx => outOfCombatWaitPoint = WoWPoint.Zero)),
                        // raid following logic kicks in when targeting is empty, we don't want that to happen so movement is disabled so CR can continue to use abilites but can not move.
                        new Decorator(
                            ctx => Targeting.Instance.IsEmpty() && !AvoidanceManager.IsRunningOutOfAvoid && ScriptHelpers.MovementEnabled,
                            new Action(ctx => ScriptHelpers.DisableMovement(() => Targeting.Instance.IsEmpty() && !AvoidanceManager.IsRunningOutOfAvoid))))));
        }

        #endregion

        #region The Fallen Protectors

        private const uint RookStonetoeId = 71475;
        private const uint HeSoftfootId = 71479;
        private const uint SunTenderheartId = 71480;

        private const uint EmbodiedMiseryId = 71476;
        private const uint EmbodiedSorrowId = 71481;
        private const uint EmbodiedGloomId = 71477;

        private const uint EmbodiedAnguishId = 71478;
        private const uint DesperationSpawnId = 71993;

        private const uint CorruptionShockMissileSpellId = 144020;
        private const int CorruptionShockSpellId = 143958;
        private const uint DefiledGroundSpellId = 143960;
        private const int CorruptionKickSpellId = 143007;
        private const uint CorruptedBrewMissileSpellId = 143021;
        private const uint NoxiousPoisonSpellId = 143235;
        private const int FixateSpellId = 143292;
        private const int ShaSearSpellId = 143423;
        private const int VengefulStrikesSpellId = 144396;
        private const int MarkOfAnguishSpellId = 143842;

        [LocationHandler(1212.693, 1031.918, 418.0828, 75, "The Fallen Protectors")]
        public Composite TheFallenProtectorsBehavior()
        {
            var encounterLoc = new WoWPoint(1212.693, 1031.918, 418.0828);

            var encounter = new TheFallenProtectorEncounter();
            // Avoid Corruption Shock Missile's landing location.
            AddAvoidLocation(ctx => true, 4, o => ((WoWMissile) o).ImpactPosition, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == CorruptionShockMissileSpellId));

            // Avoid Corruption Brew Missile's landing location.
            AddAvoidLocation(ctx => true, 5, o => ((WoWMissile) o).ImpactPosition, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == CorruptedBrewMissileSpellId));

            // Avoid defiled ground 
            AddAvoidObject(ctx => true, 8, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == DefiledGroundSpellId);

            // Avoid noxious poison pools
            AddAvoidObject(ctx => true, 3, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == NoxiousPoisonSpellId);

            // avoid corruption kick
            AddAvoidObject(ctx => true, 10, o => o.Entry == RookStonetoeId && o.ToUnit().HasAura(CorruptionKickSpellId));

            // Run away from players that have the 'Sha Sear' aura or stay away from them if bot has the aura.
            AddAvoidObject(ctx => true, 5, o => o is WoWPlayer && !o.IsMe && (Me.HasAura(ShaSearSpellId) || o.ToPlayer().HasAura(ShaSearSpellId)));

            return new PrioritySelector(
                ctx => encounter.Refresh(),
                // Transfer mark of Anguish to one of the tanks. 
                new Decorator(
                    ctx => Me.HasAura("Mark of Anguish"),
                    // set context to nearest tank that is alive.
                    new Sequence(
                        ctx =>
                            ScriptHelpers.GroupMembers.Where(g => g.IsTank && g.IsAlive && g.Location.Distance(Me.Location) <= 40)
                                .OrderBy(g => g.Location.DistanceSqr(Me.Location))
                                .FirstOrDefault(),
                        // break sequence if context is null
                        new DecoratorContinue(ctx => ctx == null, new ActionAlwaysFail()),
                        new ActionLogger("Transfering Mark of Anguish to tank"),
                        new Helpers.Action<WoWPlayer>(tank => tank.Target()),
                        // wait for target change to complete
                        new WaitContinue<WoWPlayer>(2, tank => Me.CurrentTargetGuid == tank.Guid, new ActionAlwaysSucceed()),
                        new Action(ctx => SpellActionButton.ExtraActionButton.Use()))),
                // Handle Dark Meditation;
                WaitAtLocationBehavior(ctx => encounter.DarkMeditationLocation != WoWPoint.Zero, () => encounter.DarkMeditationLocation, 8, "Dark Meditation Location"),
                // Handle InfernoStrike;
                WaitAtLocationBehavior(
                    ctx => encounter.InfernoStrikeTarget != WoWPoint.Zero && encounter.DarkMeditationLocation == WoWPoint.Zero,
                    () => encounter.InfernoStrikeTarget,
                    8,
                    "Inferno Strike Location"),
                // the frontal cone of Rook Stonetoe because of his Vengeful Strikes ability
                ScriptHelpers.CreateAvoidUnitAnglesBehavior(
                    ctx => encounter.RookStonetoe != null && encounter.RookStonetoe.Distance <= 10 && encounter.RookStonetoe.HasAura(VengefulStrikesSpellId),
                    () => encounter.RookStonetoe,
                    new ScriptHelpers.AngleSpan(0, 90)),
                // interrupt Corruption Shock spell.
                ScriptHelpers.CreateInterruptCast(() => encounter.EmbodiedGloom, CorruptionShockSpellId),
                // interrupt Sha Sear spell.
                ScriptHelpers.CreateInterruptCast(() => encounter.SunTenderheart, ShaSearSpellId),
                // dispell Shadow Word: Bane from party members before it spreads.
                ScriptHelpers.CreateDispellParty("Shadow Word: Bane", ScriptHelpers.PartyDispellType.Magic));
        }

        private class TheFallenProtectorEncounter
        {
            private const int InfernoStrikeSpellId = 143962;
            private const uint DarkMeditationSpellId = 143546;

            public WoWUnit RookStonetoe { get; private set; }
            public WoWUnit HeSoftfoot { get; private set; }
            public WoWUnit SunTenderheart { get; private set; }
            public WoWUnit EmbodiedMisery { get; private set; }
            public WoWUnit EmbodiedSorrow { get; private set; }
            public WoWUnit EmbodiedGloom { get; private set; }
            public WoWUnit EmbodiedAnguish { get; private set; }
            // returns the InfernoStrike target location or WoWPoint.Zero if there is none.
            public WoWPoint InfernoStrikeTarget { get; private set; }
            // returns the Dark Meditation location or WoWPoint.Zero if there is none.
            public WoWPoint DarkMeditationLocation { get; private set; }

            public TheFallenProtectorEncounter Refresh()
            {
                var units = ObjectManager.GetObjectsOfTypeFast<WoWUnit>();
                RookStonetoe = units.FirstOrDefault(u => u.Entry == RookStonetoeId);
                HeSoftfoot = units.FirstOrDefault(u => u.Entry == HeSoftfootId);
                SunTenderheart = units.FirstOrDefault(u => u.Entry == SunTenderheartId);
                EmbodiedMisery = units.FirstOrDefault(u => u.Entry == EmbodiedMiseryId);
                EmbodiedSorrow = units.FirstOrDefault(u => u.Entry == EmbodiedSorrowId);
                EmbodiedGloom = units.FirstOrDefault(u => u.Entry == EmbodiedGloomId);
                EmbodiedAnguish = units.FirstOrDefault(u => u.Entry == EmbodiedAnguishId);

                InfernoStrikeTarget = EmbodiedSorrow != null && EmbodiedSorrow.CastingSpellId == InfernoStrikeSpellId && EmbodiedSorrow.CurrentTargetGuid != 0
                    ? EmbodiedSorrow.CurrentTarget.Location
                    : WoWPoint.Zero;

                var darkMeditation = ObjectManager.GetObjectsOfTypeFast<WoWAreaTrigger>().FirstOrDefault(a => a.SpellId == DarkMeditationSpellId);
                DarkMeditationLocation = darkMeditation != null ? darkMeditation.Location : WoWPoint.Zero;
                return this;
            }
        }

        #endregion

        #region Scarred Vale Mini Bosses

        [EncounterHandler(72661, "Zeal")]
        [EncounterHandler(72662, "Vanity")]
        [EncounterHandler(72663, "Arrogance")]
        public Composite ScarredValeMinibossesBehavior()
        {
            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // stack on raid.
                WaitAtLocationBehavior(ctx => Me.IsRange(), () => ScriptHelpers.GetGroupCenterLocation(p => p.IsRange), 10, "Raid location"));
        }

        #endregion

        #region Norushen

        private const uint BlindHatredId = 72565;
        private const uint BottomlessPitSpellId = 146793;
        private const uint EssenceofCorruptionId = 72263;
        private const uint ManifestationofCorruptionId = 72264;
        private const uint ManifestationofCorruptionPhasedId = 71977;
        private const uint EssenceofCorruptionPhasedId = 71976;
        private const int TearRealitySpellId = 144482;

        private uint CorruptionLevel
        {
            get
            {
                var aura = Me.GetAuraByName("Corruption");
                return aura != null ? aura.StackCount : 0;
            }
        }

        [EncounterHandler(72276, "Amalgam of Corruption")]
        public Composite AmalgamOfCorruptionBehavior()
        {
            WoWUnit boss = null;
            WoWUnit blindHatred = null;
            float bossToBlindHatredAng = 0;
            var getBossToBlindHatredRadians = new Func<float>(
                () =>
                {
                    if (blindHatred == null || !blindHatred.IsValid)
                        return 0;
                    var bossLoc = boss.Location;
                    var blindHatredLoc = blindHatred.Location;
                    var bossToBlindHatredVector = blindHatredLoc - bossLoc;

                    return (float) Math.Atan2(bossToBlindHatredVector.Y, bossToBlindHatredVector.X);
                });

            var getLocationOppositeBlindHatred = new Func<WoWPoint>(
                () =>
                {
                    var minRange = 3;
                    var maxRange = Me.IsMelee() ? (int) boss.MeleeRange() : 20;
                    var ang = WoWMathHelper.NormalizeRadian(bossToBlindHatredAng + (float) Math.PI);
                    return boss.Location.RayCast(ang, ScriptHelpers.Rnd.Next(minRange, maxRange));
                });

            // stay away from the spell funnel that starts at boss and ends at Blind Hatred 
            //AddAvoidObject(
            //    ctx => boss != null && boss.IsValid,
            //    o =>
            //    {
            //        var bossloc = boss.Location;
            //        var loc = o.Location;
            //        var nearestPointBetweenBossAndBlindHatred = Me.Location.GetNearestPointOnSegment(bossloc, o.Location);
            //        return Me.IsMoving || nearestPointBetweenBossAndBlindHatred.Distance(Me.Location) < 15 ? loc.Distance(boss.Location) + 5 : 15;
            //    },
            //    o => o.Entry == BlindHatredId,
            //    o =>
            //    {
            //        var to = o.Location;
            //        var bossloc = boss.Location;
            //        var nearestPointBetweenBossAndBlindHatred = Me.Location.GetNearestPointOnSegment(bossloc, o.Location);
            //        if (nearestPointBetweenBossAndBlindHatred.Distance(Me.Location) < 15)
            //        {
            //            return Me.Location.GetNearestPointOnSegment(bossloc, o.Location);
            //        }
            //        return o.Location;
            //    });

            return new PrioritySelector(
                ctx =>
                {
                    blindHatred = ObjectManager.GetObjectsOfTypeFast<WoWUnit>().FirstOrDefault(u => u.Entry == BlindHatredId);
                    return boss = ctx as WoWUnit;
                },
                new Decorator(
                    ctx =>
                    {
                        if (blindHatred == null) return false;
                        bossToBlindHatredAng = getBossToBlindHatredRadians();
                        return WoWMathHelper.IsFacing(boss.Location, bossToBlindHatredAng, Me.Location);
                    },
                    new Sequence(ScriptHelpers.CreateMoveToContinue(ctx => true, getLocationOppositeBlindHatred, true))));
        }

        #region DPS Look Within phase

        private const int ExpelCorruptionSpellId = 144479;


        [EncounterHandler((int) ManifestationofCorruptionPhasedId, "Manifestation of Corruption Phased")]
        [EncounterHandler((int) EssenceofCorruptionPhasedId, "Essence of CorruptionPhased")]
        public Composite DPSLookWithinBehavior()
        {
            // used during dps 'Look Within' phase
            AddAvoidObject(ctx => true, 4, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == ExpelCorruptionSpellId);

            WoWUnit boss = null;
            WoWUnit myTarget = null;

            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                new Decorator(
                    ScriptHelpers.CreateAvoidUnitAnglesBehavior(
                        ctx => boss.CastingSpellId == TearRealitySpellId && ((WoWUnit) ctx).Distance < 20,
                        () => boss,
                        new ScriptHelpers.AngleSpan(0, 120))),
                // stand behind the Essence of Corruption.. 
                ScriptHelpers.GetBehindUnit(
                    ctx => (myTarget = Me.CurrentTarget) != null && myTarget.Entry == EssenceofCorruptionPhasedId && myTarget.IsFacing(Me),
                    () => myTarget));
        }

        #endregion

        #region Healer Look Within phase

        private const uint RookStonetoeLookWithinHealerSpawnId = 71996;
        private const uint SunTenderheartLookWithinHealerSpawnId = 72000;
        private const uint LevenDawnbladeLookWithinHealerSpawnId = 71995;

        [EncounterHandler((int) RookStonetoeLookWithinHealerSpawnId, "Rook Stonetoe Phased", Mode = CallBehaviorMode.Proximity)]
        [EncounterHandler((int) SunTenderheartLookWithinHealerSpawnId, "Sun Tenderheart", Mode = CallBehaviorMode.Proximity)]
        [EncounterHandler((int) LevenDawnbladeLookWithinHealerSpawnId, "Leven Dawnblade Phased", Mode = CallBehaviorMode.Proximity)]
        public Composite HealerLookWithinBehavior()
        {
            AddAvoidObject(ctx => true, 4, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == BottomlessPitSpellId);

            WoWUnit boss = null;

            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // Dispell "Lingering Corruption" from the friendly NPCs.
                CreateDispellFriendlyUnit("Lingering Corruption", ScriptHelpers.PartyDispellType.Magic, ctx => boss));
        }

        #endregion

        #endregion

        #region Sha of Pride

        private const uint BurstingPrideMissileSpellId = 144910;

        private const uint ManifestationOfPrideId = 71946;
        private const int MockingBlastSpellId = 144379;
        private const uint ReflectionId = 72172;
        private const uint NetherTempestMissileSpellId = 114956;
        private const int SwellingPrideId = 144400;
        private const uint ShadowPrisonId = 72017;
        private const uint ShaOfPrideId = 71734;

        private uint PrideLevel
        {
            get
            {
                var aura = Me.GetAuraByName("Pride");
                return aura != null ? aura.StackCount : 0;
            }
        }

        [EncounterHandler(71734, "Sha of Pride", Mode = CallBehaviorMode.Proximity, BossRange = 90)]
        public Composite ShaOfPrideBehavior()
        {
            var encounter = new ShaOfPrideEncounter();
            // Avoid 'Bursting Pride' missile impact location.
            AddAvoidLocation(ctx => true, 5, o => ((WoWMissile) o).ImpactPosition, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == BurstingPrideMissileSpellId));
            // missile strike casted by the 'Reflection' npc when they show up.
            AddAvoidLocation(ctx => true, 2, o => ((WoWMissile) o).ImpactPosition, () => WoWMissile.InFlightMissiles.Where(m => m.SpellId == NetherTempestMissileSpellId));
            // Run away from players that have the 'Aura of Pride' aura or stay away from them if bot has the aura.
            AddAvoidObject(ctx => true, 5, o => o is WoWPlayer && (o.ToPlayer().HasAura("Aura of Pride") || Me.HasAura("Aura of Pride") && !o.IsMe));
            // move away from playes when boss is casting the 'Swelling Pride' ability and player will recieve the 'Bursting Pride' or 'Aura of Pride' abilty
            AddAvoidObject(
                ctx =>
                    encounter.Boss != null && encounter.Boss.IsValid && encounter.Boss.CastingSpellId == SwellingPrideId &&
                    (PrideLevel >= 25 && PrideLevel < 50 || PrideLevel >= 75),
                5,
                o => o is WoWPlayer && !o.IsMe);
            // avoid running into the boss when he's not in combat.
            AddAvoidObject(ctx => true, 30, o => o.Entry == ShaOfPrideId && !o.ToUnit().Combat && o.ToUnit().IsAlive);

            return new PrioritySelector(
                ctx => encounter.Refresh((WoWUnit) ctx),
                // move to the party member with Gift of the Titans that is closest
                WaitAtLocationBehavior(ctx => encounter.HandleProjection, () => encounter.ProjectionLoc, 2, "Projection location"),
                // move to the party member with Gift of the Titans that is closest if not a healer or melee
                WaitAtLocationBehavior(ctx => encounter.FreePrisoner, () => encounter.ShadowPrisonLoc, 2, "Free prisoner location"),
                // move to the party member with Gift of the Titans that is closest
                WaitAtLocationBehavior(ctx => encounter.GoToGiftOfTitansMeetup, () => encounter.GiftOfTitansMeetLoc, 5, "Gift of the Titans meetup location"),
                // dispell dot off party members as long as my pride level is low. (dipelling this will increase pride level by 5)
                new Decorator(ctx => PrideLevel < 50, ScriptHelpers.CreateDispellParty("Mark of Arrogance", ScriptHelpers.PartyDispellType.Magic)),
                // interupt the 'Mocking Blast' spell that adds cast 
                ScriptHelpers.CreateInterruptCast(
                    () =>
                        ObjectManager.GetObjectsOfTypeFast<WoWUnit>()
                            .Where(u => u.Entry == ManifestationOfPrideId && u.CastingSpellId == MockingBlastSpellId)
                            .OrderBy(u => u.DistanceSqr)
                            .FirstOrDefault(),
                    MockingBlastSpellId),
                // all range should stack up when not handling prisoners, Gift of Titans or projection.
                WaitAtLocationBehavior(
                    ctx => Me.IsRange() && !encounter.FreePrisoner && !encounter.HandleProjection && !encounter.GoToGiftOfTitansMeetup,
                    () => ScriptHelpers.GetGroupCenterLocation(p => p.IsRange),
                    13,
                    "Raid location"));
        }


        private class ShaOfPrideEncounter
        {
            private const int ProjectionMissileSpellId = 145066;
            public WoWUnit Boss { get; private set; }
            public WoWPoint ShadowPrisonLoc { get; private set; }
            public WoWPoint ProjectionLoc { get; private set; }
            public WoWPoint GiftOfTitansMeetLoc { get; private set; }

            public bool FreePrisoner
            {
                get { return !Me.IsHealer() && !HandleProjection && ShadowPrisonLoc != WoWPoint.Zero; }
            }

            public bool GoToGiftOfTitansMeetup
            {
                get { return !FreePrisoner && GiftOfTitansMeetLoc != WoWPoint.Zero; }
            }

            public bool HandleProjection
            {
                get { return ProjectionLoc != WoWPoint.Zero; }
            }

            public ShaOfPrideEncounter Refresh(WoWUnit boss)
            {
                Boss = boss;
                var raidmembers = Me.RaidMembers;

                var myLoc = Me.Location;

                ShadowPrisonLoc = (from groupMember in raidmembers
                    where groupMember.HasAura("Corrupted Prison")
                    let groupMemberLocation = groupMember.Location
                    let shadowPrisons = (from unit in ObjectManager.GetObjectsOfTypeFast<WoWUnit>()
                        where unit.Entry == ShadowPrisonId
                        let unitLoc = unit.Location
                        where unitLoc.Distance(groupMemberLocation) < 20 && !raidmembers.Any(r => r != groupMember && r.Location.Distance(unitLoc) < 6)
                        select new {Unit = unit, Location = unitLoc}).ToList()
                    where shadowPrisons.Any()
                    select shadowPrisons).SelectMany(p => p).Select(p => p.Location).OrderBy(l => l.DistanceSqr(myLoc)).FirstOrDefault();

                ProjectionLoc =
                    WoWMissile.InFlightMissiles.Where(m => m.SpellId == ProjectionMissileSpellId && m.CasterGuid == Me.Guid).Select(m => m.ImpactPosition).FirstOrDefault();

                GiftOfTitansMeetLoc = Me.HasAura("Gift of the Titans") ? GetGiftOfTheTitanMeetLoc() : WoWPoint.Zero;
                return this;
            }

            private static WoWPoint GetGiftOfTheTitanMeetLoc()
            {
                var myLoc = Me.Location;
                var locs = Me.RaidMembers.Where(r => !r.IsMe && r.HasAura("Gift of the Titans")).Select(r => r.Location).OrderBy(l => l.DistanceSqr(myLoc)).ToList();
                if (!locs.Any())
                    return WoWPoint.Zero;
                var avgLoc = new WoWPoint(locs.Select(l => l.X).Average(), locs.Select(l => l.Y).Average(), locs.Select(l => l.Z).Average());
                if (Targeting.Instance.IsEmpty() || IsUnitWithinRange(Targeting.Instance.FirstUnit, avgLoc, 8))
                    return avgLoc;
                return WoWPoint.Zero;
            }
        }

        #endregion

        #region Misc

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }

        private WoWPoint GetRandomPointAroundLocation(WoWPoint point, int startAng, int endAng, float minDist, float maxDist)
        {
            if (endAng < startAng)
                endAng += 360;
            var randomRadians = WoWMathHelper.NormalizeRadian(WoWMathHelper.DegreesToRadians(ScriptHelpers.Rnd.Next(startAng, endAng)));
            var randomDist = ScriptHelpers.Rnd.Next((int) minDist, (int) maxDist);
            return point.RayCast(randomRadians, randomDist);
        }

        private Composite WaitAtLocationBehavior(CanRunDecoratorDelegate canRun, Func<WoWPoint> locationSelector, float precision, string locationDescription = null)
        {
            WoWPoint location = WoWPoint.Zero;

            return new Decorator(
                canRun,
                new PrioritySelector(
                    ctx => location = locationSelector(),
                    // move within precision distance of location.
                    new Decorator<WoWPoint>(
                        loc => Me.Location.Distance(loc) > precision,
                        new PrioritySelector(
                            new ActionSetActivity(string.Format("Moving to {0} ", !string.IsNullOrEmpty(locationDescription) ? locationDescription : location.ToString())),
                            new Helpers.Action<WoWPoint>(loc => Navigator.MoveTo(loc)))),
                    new Decorator<WoWPoint>(
                        loc => Me.Location.Distance(loc) <= precision,
                        new PrioritySelector(
                            new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())),
                            // disable movement for healers if CR is handling movement to avoid movement conflictions..
                            new Decorator(
                                ctx => Me.IsHealer() && ScriptHelpers.MovementEnabled && !DungeonBuddySettings.Instance.CombatMovement,
                                new Action(ctx => ScriptHelpers.DisableMovement(() => canRun(ctx)))),
                            // cause behavior to stop here if bot's dps/heal target is not within range.

                            new Decorator(
                                ctx =>
                                {
                                    if (Me.IsHealer())
                                        return DungeonBuddySettings.Instance.CombatMovement && !IsUnitWithinRange(HealTargeting.Instance.FirstUnit, location, precision);
                                    return !IsUnitWithinRange(Targeting.Instance.FirstUnit, location, precision);
                                },
                                new ActionAlwaysSucceed())))));
        }

        private static bool IsUnitWithinRange(WoWUnit unit, WoWPoint location, float movementRadius)
        {
            if (unit == null)
                return false;
            var range = Me.IsMelee() ? unit.MeleeRange() + movementRadius : unit.CombatReach + 35 + movementRadius;
            return unit.Location.Distance(location) <= range;
        }

        // todo: remove once this is added in ScriptHelpers.
        public static Composite CreateDispellFriendlyUnit(string spellName, ScriptHelpers.PartyDispellType dispellType, Func<object, WoWUnit> targetSelector)
        {
            WoWSpell dispellSpell = null;
            WoWUnit dispellTarget = null;
            Func<WoWSpell> getSpell = () => null;

            switch (dispellType)
            {
                case ScriptHelpers.PartyDispellType.Curse:
                    getSpell = ScriptHelpers.GetCurseDispell;
                    break;
                case ScriptHelpers.PartyDispellType.Disease:
                    getSpell = ScriptHelpers.GetDiseaseDispell;
                    break;
                case ScriptHelpers.PartyDispellType.Magic:
                    getSpell = ScriptHelpers.GetDefensiveMagicDispell;
                    break;
                case ScriptHelpers.PartyDispellType.Poison:
                    getSpell = ScriptHelpers.GetPoisonDispell;
                    break;
            }

            return new PrioritySelector(
                ctx =>
                {
                    dispellTarget = targetSelector(ctx);
                    if (dispellTarget != null)
                        dispellSpell = getSpell();
                    return ctx;
                },
                new Decorator(
                    ctx => dispellTarget != null && dispellTarget.HasAura(spellName) && dispellSpell != null && SpellManager.CanCast(dispellSpell, false),
                    new PrioritySelector(
                        new Decorator(
                            ctx => dispellTarget.Distance > dispellSpell.ActualMaxRange(dispellTarget),
                            new Action(ctx => Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(dispellTarget.Location)))),
                        new Decorator(
                            ctx => dispellTarget.Distance <= dispellSpell.ActualMaxRange(dispellTarget),
                            new Sequence(
                                new Action(
                                    ctx =>
                                        Logger.Write(
                                            "Dispelling {0} effect {1} on {2}",
                                            dispellType,
                                            spellName,
                                            dispellTarget is WoWPlayer ? ((WoWPlayer) dispellTarget).UnitId() : dispellTarget.Name)),
                                new Action(ctx => SpellManager.Cast(dispellSpell, dispellTarget)))))));
        }

        #endregion
    }
}